﻿using MimeKit;
using MimeKit.Text;
using PMS.Core.Configuration.CommonEnumConfigs;
using PMS.Core.Infrastructure;
using PMS.Core.Utils;
using PMS.Data.Entities.Messages;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace PMS.Services.Utils.Messages
{
    /// <summary>
    /// Email sender
    /// </summary>
    public partial class AliyunEmailSender : IEmailSender
    {
        #region Fields

        private readonly ISmtpBuilder _smtpBuilder;
        private readonly IPMSFileProvider _fileProvider;

        #endregion

        #region Ctor

        public AliyunEmailSender(ISmtpBuilder smtpBuilder,
            IPMSFileProvider fileProvider)
        {
            _smtpBuilder = smtpBuilder;
            _fileProvider = fileProvider;
        }

        #endregion

        #region Utilities

        /// <summary>
        /// Create an file attachment for the specific file path
        /// </summary>
        /// <param name="filePath">Attachment file path</param>
        /// <param name="attachmentFileName">Attachment file name</param>
        /// <returns>A leaf-node MIME part that contains an attachment.</returns>
        protected MimePart CreateMimeAttachment(string filePath, string attachmentFileName = null)
        {
            if (string.IsNullOrWhiteSpace(filePath))
                throw new ArgumentNullException(nameof(filePath));

            if (string.IsNullOrWhiteSpace(attachmentFileName))
                attachmentFileName = Path.GetFileName(filePath);

            return CreateMimeAttachment(
                    attachmentFileName,
                    _fileProvider.ReadAllBytes(filePath),
                    _fileProvider.GetCreationTime(filePath),
                    _fileProvider.GetLastWriteTime(filePath),
                    _fileProvider.GetLastAccessTime(filePath));
        }

        /// <summary>
        /// Create an file attachment for the binary data
        /// </summary>
        /// <param name="attachmentFileName">Attachment file name</param>
        /// <param name="binaryContent">The array of unsigned bytes from which to create the attachment stream.</param>
        /// <param name="cDate">Creation date and time for the specified file or directory</param>
        /// <param name="mDate">Date and time that the specified file or directory was last written to</param>
        /// <param name="rDate">Date and time that the specified file or directory was last access to.</param>
        /// <returns>A leaf-node MIME part that contains an attachment.</returns>
        protected MimePart CreateMimeAttachment(string attachmentFileName, byte[] binaryContent, DateTime cDate, DateTime mDate, DateTime rDate)
        {
            if (!ContentType.TryParse(MimeTypes.GetMimeType(attachmentFileName), out var mimeContentType))
                mimeContentType = new ContentType("application", "octet-stream");

            return new MimePart(mimeContentType)
            {
                FileName = attachmentFileName,
                Content = new MimeContent(new MemoryStream(binaryContent), ContentEncoding.Default),
                ContentDisposition = new ContentDisposition
                {
                    CreationDate = cDate,
                    ModificationDate = mDate,
                    ReadDate = rDate
                }
            };
        }

        #endregion

        #region Methods

        /// <summary>
        /// Sends an email 单邮件发送/触发
        /// </summary>
        /// <param name="emailAccount">Email account to use</param>
        /// <param name="subject">Subject</param>
        /// <param name="body">Body</param>
        /// <param name="fromAddress">use emailAccount.Email</param>
        /// <param name="fromName">use emailAccount.DisplayName</param>
        /// <param name="toAddress">To address</param>
        /// <param name="toName">To display name</param>
        /// <param name="replyToAddress">use emailAccount.ReplyTo</param>
        /// <param name="replyToName">use emailAccount.DisplayName</param>
        /// <param name="bcc">
        ///     BCC addresses list Blind Carbon Copy(暗抄送)
        ///     Key: Email Address
        ///     Value: Email DisplayName
        /// </param>
        /// <param name="cc">
        ///     CC addresses list Carbon Copy(抄送) 
        ///     Key: Email Address
        ///     Value: Email DisplayName
        /// </param>
        /// <param name="attachmentFilePath">Attachment file path</param>
        /// <param name="attachmentFileName">Attachment file name. If specified, then this file name will be sent to a recipient. Otherwise, "AttachmentFilePath" name will be used.</param>
        /// <param name="headers">Headers</param>
        public virtual void SendEmail(EmailAccount emailAccount, string subject, string body,
            string fromAddress, string fromName,
            string toAddress, string toName,
            string replyToAddress, string replyToName,
            IDictionary<string, string> bcc = null, IDictionary<string, string> cc = null,
            string attachmentFilePath = null, string attachmentFileName = null,
            IDictionary<string, string> headers = null)
        {
            if(emailAccount is null)
                throw new PMSException("Aliyun Sender Account can not be null.", SystemLogLevel.Error);

            try
            {
                var message = new MimeMessage();
                if (string.IsNullOrEmpty(fromAddress)) fromAddress = emailAccount.Email;
                if (string.IsNullOrEmpty(fromName)) fromName = emailAccount.DisplayName;
                message.From.Add(new MailboxAddress(Encoding.UTF8, fromName, fromAddress));
                message.To.Add(new MailboxAddress(Encoding.UTF8, toName, toAddress));

                if (string.IsNullOrEmpty(replyToAddress))
                    replyToAddress = emailAccount.ReplyTo;

                if (!string.IsNullOrEmpty(replyToAddress))
                {
                    if (string.IsNullOrEmpty(replyToName))
                        replyToName = emailAccount.DisplayName;
                    message.ReplyTo.Add(new MailboxAddress(Encoding.UTF8, replyToName, replyToAddress));
                }

                //BCC
                if (bcc != null)
                {
                    foreach (var (address, name) in bcc)
                    {
                        if (string.IsNullOrWhiteSpace(address) || !CommonHelper.IsValidEmail(address)) continue;
                        message.Bcc.Add(new MailboxAddress(name.Trim(), address.Trim()));
                    }
                }

                //CC
                if (cc != null)
                {
                    foreach (var (address, name) in cc)
                    {
                        if (string.IsNullOrWhiteSpace(address) || !CommonHelper.IsValidEmail(address)) continue;
                        message.Cc.Add(new MailboxAddress(name.Trim(), address.Trim()));
                    }
                }

                //content
                message.Subject = subject;

                //headers
                if (headers != null)
                    foreach (var header in headers)
                    {
                        message.Headers.Add(header.Key, header.Value);
                    }

                var multipart = new Multipart("mixed")
                {
                    new TextPart(TextFormat.Html) { Text = body }
                };

                //create the file attachment for this e-mail message
                if (!string.IsNullOrEmpty(attachmentFilePath) && _fileProvider.FileExists(attachmentFilePath))
                {
                    multipart.Add(CreateMimeAttachment(attachmentFilePath, attachmentFileName));
                }

                message.Body = multipart;

                //send email
                using var smtpClient = _smtpBuilder.Build(emailAccount);
                smtpClient.Send(message);
                smtpClient.Disconnect(true);
            }
            catch(Exception ex)
            {
                if (ex is PMSException) {
                    if(((PMSException)ex).LogLevel == SystemLogLevel.Warning) return;
                }
                //else 
                throw new PMSException(ex.Message, ex);
            }
        }

        #endregion
    }
}
